/*-
 * Copyright 2009-2012 Guram Dukashvili
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
//---------------------------------------------------------------------------
#include "sys.h"
#include "rwlock.h"
#if HAVE_INTRIN_H
#include <intrin.h>
#endif 
#if HAVE_MACHINE_ATOMIC_H
#include <machine/atomic.h>
#endif
#if HAVE_MACHINE_CPU_H
#include <machine/cpu.h>
#endif
//---------------------------------------------------------------------------
void rwlock_init(struct rwlock * rwl)
{
  semaphore_init_v(&rwl->lock,1);
  semaphore_init(&rwl->write);
  rwl->readers = rwl->write_waiters = 0;
  rwl->writer = 0;
}
//---------------------------------------------------------------------------
struct rwlock * rwlock_new(void)
{
  struct rwlock * rwl = salloc(sizeof(struct rwlock),NULL);
  if( rwl != NULL )
    rwlock_init(rwl);
  return rwl;
}
//---------------------------------------------------------------------------
void rwlock_destroy(struct rwlock * rwl)
{
#if COMPILE_RUNTIME_CHECKS
  if( rwl->readers != 0 || rwl->writer != 0 ){
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
    abort();
  }
#endif
  semaphore_destroy(&rwl->write);
  semaphore_destroy(&rwl->lock);
}
//---------------------------------------------------------------------------
void rwlock_delete(struct rwlock * rwl)
{
  rwlock_destroy(rwl);
  sfree(rwl);
}
//---------------------------------------------------------------------------
bool rwlock_try_acquire_read_lock(struct rwlock * rwl)
{
  bool r = semaphore_try_acquire(&rwl->lock);
  if( r ){
    rwl->readers++;
    semaphore_release(&rwl->lock);
  }
  return r;
}
//---------------------------------------------------------------------------
void rwlock_acquire_read_lock(struct rwlock * rwl)
{
  semaphore_acquire(&rwl->lock);
  rwl->readers++;
  semaphore_release(&rwl->lock);
}
//---------------------------------------------------------------------------
bool rwlock_try_acquire_write_lock(struct rwlock * rwl)
{
  bool w = semaphore_try_acquire(&rwl->lock);
  if( w ){
    if( rwl->readers == 0 ){
      rwl->writer = portable_gettid();
    }
    else {
      semaphore_release(&rwl->lock);
      w = false;
    }
  }
  return w;
}
//---------------------------------------------------------------------------
void rwlock_acquire_write_lock(struct rwlock * rwl)
{
  semaphore_acquire(&rwl->lock);
  if( rwl->readers != 0 ){
    rwl->write_waiters++;
    do {
      semaphore_release(&rwl->lock);
      semaphore_wait(&rwl->write);
    } while( rwl->readers != 0 );
    rwl->write_waiters--;
  }
  rwl->writer = portable_gettid();
}
//---------------------------------------------------------------------------
bool rwlock_try_upgrade(struct rwlock * rwl)
{
  bool w;

#if COMPILE_RUNTIME_CHECKS
  if( rwl->writer == portable_gettid() ){
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
    abort();
  }
#endif
  semaphore_acquire(&rwl->lock);
#if COMPILE_RUNTIME_CHECKS
  if( rwl->readers == 0 ){
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
    abort();
  }
#endif
  rwl->readers--;
  w = rwl->readers == 0;
  if( w ){
    rwl->writer = portable_gettid();
  }
  else {
    rwl->readers++;
    semaphore_release(&rwl->lock);
    w = false;
  }
  return w;
}
//---------------------------------------------------------------------------
void rwlock_upgrade(struct rwlock * rwl)
{
  semaphore_acquire(&rwl->lock);
#if COMPILE_RUNTIME_CHECKS
  if( rwl->readers == 0 ){
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
    abort();
  }
#endif

  // first drop read lock
  rwl->readers--;

  if( rwl->readers != 0 ){
    rwl->write_waiters++;
    do {
      semaphore_release(&rwl->lock);
      semaphore_wait(&rwl->write);
    } while( rwl->readers != 0 );
    rwl->write_waiters--;
  }
  rwl->writer = portable_gettid();
}
//---------------------------------------------------------------------------
void rwlock_downgrade(struct rwlock * rwl)
{
#if COMPILE_RUNTIME_CHECKS
  if( rwl->readers != 0 ){
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
    abort();
  }
  if( portable_gettid() != rwl->writer ){
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
    abort();
  }
#endif
  rwl->readers = 1;
  rwl->writer = 0;
  semaphore_release(&rwl->lock);
}
//---------------------------------------------------------------------------
void rwlock_release_read_lock(struct rwlock * rwl)
{
  semaphore_acquire(&rwl->lock);
#if COMPILE_RUNTIME_CHECKS
  if( rwl->readers == 0 ){
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
    abort();
  }
  if( portable_gettid() == rwl->writer ){
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
    abort();
  }
#endif
  rwl->readers--;
  if( rwl->readers == 0 ){
    if( rwl->write_waiters != 0 ){
      semaphore_post(&rwl->write);
    }
    else {
      semaphore_release(&rwl->lock);
    }
  }
  else {
    semaphore_release(&rwl->lock);
  }
}
//---------------------------------------------------------------------------
void rwlock_release_write_lock(struct rwlock * rwl)
{
#if COMPILE_RUNTIME_CHECKS
  if( rwl->readers != 0 ){
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
    abort();
  }
  if( portable_gettid() != rwl->writer ){
    fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
    abort();
  }
#endif
  rwl->writer = 0;
  if( rwl->write_waiters != 0 ){
    semaphore_post(&rwl->write);
  }
  else {
    semaphore_release(&rwl->lock);
  }
}
//---------------------------------------------------------------------------
void rwlock_acquire(struct rwlock * rwl,enum RWLOCK_TYPE lock_type)
{
  switch( lock_type ){
    case RWL_NONE      :
      break;
    case RWL_UPGRADE   :
      rwlock_upgrade(rwl);
      break;
    case RWL_DOWNGRADE :
      rwlock_downgrade(rwl);
      break;
    case RWL_READ      :
      rwlock_acquire_read_lock(rwl);
      break;
    case RWL_WRITE     :
      rwlock_acquire_write_lock(rwl);
      break;
#if COMPILE_RUNTIME_CHECKS
    default:
      fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
      abort();
#endif
  }
}
//---------------------------------------------------------------------------
void rwlock_release(struct rwlock * rwl,enum RWLOCK_TYPE lock_type)
{
  switch( lock_type ){
    case RWL_NONE      :
      break;
    case RWL_UPGRADE   :
    case RWL_READ      :
      rwlock_release_read_lock(rwl);
      break;
    case RWL_DOWNGRADE :
    case RWL_WRITE     :
      rwlock_release_write_lock(rwl);
      break;
#if COMPILE_RUNTIME_CHECKS
    default:
      fprintf(stderr,"undefined behavior in %s, %s, line %d\n",__PRETTY_FUNCTION__,__FILE__,__LINE__);
      abort();
#endif
  }
}
//---------------------------------------------------------------------------
